#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

//全局变量存储命令行参数及其个数
int g_argc = 0;
char* g_argv[128];

//全局的当前工作路径
char cwd[256];

//全局变量记录上一个程序的退出码
int exit_code;

//获取当前用户名
const char* GetUserName()
{
    const char* name = getenv("USER");
    return name == nullptr ? "none" : name;
}

//获取当前主机名
const char* GetHostName()
{
    const char* name = getenv("HOSTNAME");
    return name == nullptr ? "none" : name;
}

//获取当前工作路径
const char* GetPwd()
{
    const char* pwd = getenv("PWD");
    return pwd == nullptr ? "none" : pwd;
}

//获取用户的家目录
const char* GetHome()
{
    const char* home = getenv("HOME");
    return home == nullptr ? "" : home;
}

//创建并打印命令行提示符
void PrintCommandPrompt()
{
    char CommandPrompt[1024];
    
    //处理当前工作路径
    std::string pwd = GetPwd();
    if(pwd != "/") // 如果是根目录，则直接输出
        pwd = pwd.substr(pwd.rfind('/') + 1); // 查找最后一个"/"，并从下一个位置开始分割

    //这里为了区分Bash，用大括号
    snprintf(CommandPrompt, sizeof CommandPrompt,"{%s@%s %s}@ " , GetUserName(), GetHostName(), pwd.c_str());
    std::cout << CommandPrompt << std::flush; // 打印并刷新缓冲区
}

//获取用户输入的命令行
bool GetCommandLine(char* command, int size)
{
    //从键盘读取命令
    if(fgets(command, size, stdin) == nullptr)
    {
        return false;
    }

    //注意清理末尾的'\n'
    if(strlen(command) == 0) return false;
    command[strlen(command) - 1] = '\0';
    return true;
}

//命令行解析
bool CommandParse(char* command)
{
    g_argc = 0;
    for(char* p = strtok(command, " "); p != nullptr; p = strtok(nullptr, " "))
    {
        g_argv[g_argc++] = p;
    }
    return g_argc == 0 ? false : true;
}

void Cd()
{
    std::string dst;
    if(g_argc == 1 || g_argv[1] == std::string("~")) // 处理进入家目录的情况
    {
        dst = GetHome();
        if(dst == "") return;
    }
    else
    {
        dst = g_argv[1];
    }
    chdir(dst.c_str());

    //同时修改环境变量中的当前工作路径
    char tmp[128];
    if(getcwd(tmp, sizeof tmp))
    {
        snprintf(cwd, sizeof cwd, "PWD=%s", tmp);
        putenv(cwd);
    }
}

void Echo()
{
    std::string op = g_argv[1];
    if(op == "$?") // 打印上一个程序的退出码
    {
        std::cout << exit_code << std::endl;
        exit_code = 0;
    }
    else if(op[0] == '$') // 打印环境变量的值
    {
        //从"$"后的第一个字符开始，获取环境变量名
        std::string name = op.substr(1);

        //用getenv获取环境变量值
        const char* value = getenv(name.c_str());
        if(value)
            std::cout << value << std::endl;
    }
    else // 打印字符串
    {
        std::cout << op << std::endl;
    }
}

//内建命令处理
bool CheckAndExecBuiltin()
{
    //取出命令行参数表的首元素，判断是否为内建命令，如果是，则直接执行
    std::string str = g_argv[0];
    if(str == "cd")
    {
        Cd();
        return true;
    }
    else if(str == "echo")
    {
        Echo();
        return true;
    }
    //else if(...)
    return false; // 不是内建命令，直接返回
}

//执行非内建命令
void Execute()
{
    //创建子进程
    pid_t id = fork();
    if(id < 0)
    {
        //创建子进程失败，直接退出
        exit(1);
    }
    else if(id == 0)
    {
        //子进程 -- 通过程序替换执行指令
        execvp(g_argv[0], g_argv);
    }
    else
    {
        //父进程 -- 等待子进程，并获取子进程的退出信息
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if(rid > 0) // 等待成功
        {
            //将退出信息存入全局exit_code
            exit_code = WEXITSTATUS(status);
        }
    }
}

int main()
{
    while(true)
    {
        //打印命令行提示符
        PrintCommandPrompt();

        //读入命令
        char command[1024];
        if(!GetCommandLine(command, sizeof command)) // 若读取错误，就continue重新读取
        {
            std::cout << "输入错误" << std::endl;
            continue;
        }
        // //打印输入的命令行
        // std::cout << command << std::endl;

        //命令行解析
        if(!CommandParse(command))
        {
            std::cout << "命令行解析失败" << std::endl;
            continue;
        }
        // //打印解析结果
        // for(int i = 0; i < g_argc; i++)
        // {
        //     std::cout << g_argv[i] << std::endl;
        // }

        //内建命令的处理
        if(CheckAndExecBuiltin())
        {
            continue; // 是内建命令，执行完毕就回去重新打印提示符
        }

        //不是内建命令，由子进程处理
        Execute();
    }
    return 0;
}
